package importer

import (
	"context"
	"dash/collectors"
	"database/sql"
	"encoding/json"
	"github.com/go-kit/kit/log"
	"github.com/olivere/elastic/v7"
	"strings"
)

type Repository interface {
	SaveMessage(ctx context.Context, report collectors.MessageReport) error
	SaveCollectorJoinChatroom(ctx context.Context, report collectors.CollectorJoinChatroomReport) error
	SaveCollectorLeaveChatroom(ctx context.Context, report collectors.CollectorLeaveChatroomReport) error
	SaveMemberJoinChatroom(ctx context.Context, report collectors.MemberJoinChatroomReport) error
	SaveMemberLeaveChatroom(ctx context.Context, report collectors.MemberLeaveChatroomReport) error
	SaveChatroomMember(ctx context.Context, report collectors.ChatroomMemberReport) error
}

type repository struct {
	logger       log.Logger
	esClient     *elastic.Client
	mysqlDB      *sql.DB
	messageIndex string
}

func NewRepository(logger log.Logger, esClient *elastic.Client, mysqlDB *sql.DB, messageIndex string) Repository {
	return &repository{
		logger:       logger,
		esClient:     esClient,
		mysqlDB:      mysqlDB,
		messageIndex: messageIndex,
	}
}

func (r *repository) SaveMessage(ctx context.Context, report collectors.MessageReport) error {
	switch report.Type {
	case "text":
	case "audio", "video", "image":
		report.Format = report.Type
		if err := r.saveFile(ctx, report); err != nil {
			_ = r.logger.Log("err", err.Error())
		}
	case "file":
		report.Format = "file"
		n := strings.LastIndex(report.Filename, ".")
		if n > 0 {
			switch strings.ToLower(report.Filename[n+1:]) {
			case "doc", "docx":
				report.Format = "word"
			case "xls", "xlsx":
				report.Format = "excel"
			case "ppt", "pptx":
				report.Format = "ppt"
			case "txt":
				report.Format = "txt"
			case "pdf":
				report.Format = "pdf"
			case "zip", "rar":
				report.Format = "zip"
			}
		}
		if err := r.saveFile(ctx, report); err != nil {
			_ = r.logger.Log("err", err.Error())
		}
	}
	_, err := r.esClient.Index().Index(r.messageIndex).Id(report.Id).BodyJson(report).Do(ctx)
	if err != nil {
		return err
	}
	if err := r.saveMember(ctx, []collectors.Member{report.Member}); err != nil {
		return err
	}
	if err := r.saveChatroom(ctx, report.Chatroom); err != nil {
		return err
	}
	return nil
}

func (r *repository) saveFile(ctx context.Context, report collectors.MessageReport) error {
	_, err := r.mysqlDB.ExecContext(
		ctx,
		`INSERT INTO file (
                 id,
                 name,
        	     firstSeenTimestamp,
                 lastSeenTimestamp,
                 size,
                 md5,
                 type,
                 format,
                 path
             ) VALUES (
                 ?,
                 ?,
                 ?,
                 ?,
                 ?,
                 ?,
                 ?,
                 ?,
                 ?
             ) ON DUPLICATE KEY UPDATE 
                 lastSeenTimestamp = ?;`,
		report.FilePath,
		report.Filename,
		report.CaptureTimestamp,
		report.CaptureTimestamp,
		report.FileSize,
		report.FileMd5,
		report.Type,
		report.Type,
		report.FilePath,
		report.CaptureTimestamp,
	)
	return err
}

func (r *repository) saveMember(ctx context.Context, members []collectors.Member) error {
	memberSql := `INSERT INTO member (
                      id,
                      nickname,
                      avatar,
                      signature,
                      usedSignature,
                      sex,
                      dataSource,
                      address
                  ) VALUES (
                      ?,
                      ?,
                      ?,
                      ?,
                      ?,
                      ?,
                      ?,
                      ?
                  ) ON DUPLICATE KEY UPDATE
			          nickname = ?,
			          avatar = ?,
			          signature = ?,
			          usedSignature = ?,
			          sex = ?,
			          dataSource = ?,
			          address = ?;`
	stmt, err := r.mysqlDB.Prepare(memberSql)
	if err != nil {
		return err
	}
	defer func() {
		_ = stmt.Close()
	}()
	for i := 0; i < len(members); i++ {
		usedSignature, _ := json.Marshal([]string{members[i].Signature})
		address, _ := json.Marshal(members[i].Address)
		if _, err := r.mysqlDB.ExecContext(
			ctx,
			memberSql,
			members[i].Id,
			members[i].Nickname,
			members[i].Avatar,
			members[i].Signature,
			usedSignature,
			members[i].Sex,
			"微信",
			address,

			members[i].Nickname,
			members[i].Avatar,
			members[i].Signature,
			usedSignature,
			members[i].Sex,
			"微信",
			address,
		); err != nil {
			_ = r.logger.Log("err", err.Error())
		}
	}
	return nil
}

func (r *repository) saveChatroom(ctx context.Context, chatroom collectors.Chatroom) error {
	chatroomSql := `INSERT INTO chatroom (
                        id,
                        name,
                        avatar,
                        ownerId,
                        dataSource,
                        captureTimeStamp
                    ) VALUES (
                        ?,
                        ?,
                        ?,
                        ?,
                        ?,
                        ?
                    ) ON DUPLICATE KEY UPDATE 
                        name = ?,
                        avatar = ?,
                        ownerId = ?,
                        dataSource = ?;`
	_, err := r.mysqlDB.ExecContext(
		ctx,
		chatroomSql,

		chatroom.Id,
		chatroom.Name,
		chatroom.Avatar,
		chatroom.Owner.Id,
		"微信",
		chatroom.CaptureTimestamp,

		chatroom.Name,
		chatroom.Avatar,
		chatroom.Owner.Id,
		"微信",
	)
	return err
}

func (r *repository) saveChatroomMember(ctx context.Context, chatroom collectors.Chatroom, members []collectors.Member) error {
	stmt, err := r.mysqlDB.Prepare(`REPLACE INTO chatroom_member (chatroomId, memberId, alias, quit) VALUES (?, ?, ?, ?);`)
	if err != nil {
		return err
	}
	defer func() {
		if err := stmt.Close(); err != nil {
			_ = r.logger.Log("err", err.Error())
		}
	}()
	for i := 0; i < len(members); i++ {
		if _, err := stmt.ExecContext(
			ctx,
			chatroom.Id,
			members[i].Id,
			members[i].Nickname,
			false,
		); err != nil {
			_ = r.logger.Log("err", err.Error())
		}
	}
	return nil
}

func (r *repository) removeChatroomMember(ctx context.Context, chatroom collectors.Chatroom, member collectors.Member) error {
	_, err := r.mysqlDB.ExecContext(
		ctx,
		`INSERT INTO chatroom_member (chatroomId, memberId, quit) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE quit = ?;`,
		chatroom.Id, member.Id, true,
		true,
	)
	return err
}

func (r *repository) saveCollectorMembers(ctx context.Context, collectorId string, memberIds []string) error {
	stmt, err := r.mysqlDB.Prepare(`INSERT IGNORE INTO collector_member (collectorId, memberId) VALUES (?, ?);`)
	if err != nil {
		return err
	}
	defer func() {
		_ = stmt.Close()
	}()
	for _, memberId := range memberIds {
		if _, err = stmt.ExecContext(ctx, collectorId, memberId); err != nil {
			_ = r.logger.Log("err", err.Error())
		}
	}
	return err
}

func (r *repository) saveCollectorChatrooms(ctx context.Context, collectorId string, chatroomIds []string) error {
	stmt, err := r.mysqlDB.Prepare(`INSERT IGNORE INTO collector_chatroom (collectorId, chatroomId) VALUES (?, ?);`)
	if err != nil {
		return err
	}
	defer func() {
		_ = stmt.Close()
	}()
	for _, chatroomId := range chatroomIds {
		if _, err = stmt.ExecContext(ctx, collectorId, chatroomId); err != nil {
			_ = r.logger.Log("err", err.Error())
		}
	}
	return err
}

func (r *repository) SaveCollectorJoinChatroom(ctx context.Context, report collectors.CollectorJoinChatroomReport) error {
	if err := r.saveMember(ctx, []collectors.Member{report.Chatroom.Owner}); err != nil {
		return err
	}
	if err := r.saveChatroom(ctx, report.Chatroom); err != nil {
		return err
	}
	if err := r.saveChatroomMember(ctx, report.Chatroom, []collectors.Member{report.Chatroom.Owner}); err != nil {
		return err
	}
	if err := r.saveCollectorChatrooms(ctx, report.CollectorId, []string{report.Chatroom.Id}); err != nil {
		return err
	}
	if err := r.saveCollectorMembers(ctx, report.CollectorId, []string{report.Chatroom.Owner.Id}); err != nil {
		return err
	}
	return nil
}

// TODO
func (r *repository) SaveCollectorLeaveChatroom(_ context.Context, _ collectors.CollectorLeaveChatroomReport) error {
	return nil
}

func (r *repository) SaveMemberJoinChatroom(ctx context.Context, report collectors.MemberJoinChatroomReport) error {
	if err := r.saveMember(ctx, []collectors.Member{report.Chatroom.Owner, report.Member}); err != nil {
		return err
	}
	if err := r.saveChatroom(ctx, report.Chatroom); err != nil {
		return err
	}
	if err := r.saveChatroomMember(ctx, report.Chatroom, []collectors.Member{report.Chatroom.Owner, report.Member}); err != nil {
		return err
	}
	if err := r.saveCollectorChatrooms(ctx, report.CollectorId, []string{report.Chatroom.Id}); err != nil {
		return err
	}
	if err := r.saveCollectorMembers(ctx, report.CollectorId, []string{report.Chatroom.Owner.Id, report.Member.Id}); err != nil {
		return err
	}
	return nil
}

func (r *repository) SaveMemberLeaveChatroom(ctx context.Context, report collectors.MemberLeaveChatroomReport) error {
	return r.removeChatroomMember(ctx, report.Chatroom, report.Member)
}

func (r *repository) SaveChatroomMember(ctx context.Context, report collectors.ChatroomMemberReport) error {
	var chatroomIds []string
	var memberIds []string
	for i := 0; i < len(report.Chatrooms); i++ {
		chatroomIds = append(chatroomIds, report.Chatrooms[i].Id)
		memberIds = append(memberIds, report.Chatrooms[i].Owner.Id)
		report.Chatrooms[i].CaptureTimestamp = report.CaptureTimestamp
		if err := r.saveChatroom(ctx, report.Chatrooms[i]); err != nil {
			_ = r.logger.Log("err", err.Error())
			return err
		}
		if err := r.saveMember(ctx, append(report.Chatrooms[i].Members, report.Chatrooms[i].Owner)); err != nil {
			_ = r.logger.Log("err", err.Error())
			return err
		}
		for j := 0; j < len(report.Chatrooms[i].Members); j++ {
			memberIds = append(memberIds, report.Chatrooms[i].Members[j].Id)
		}
		if err := r.saveChatroomMember(ctx, report.Chatrooms[i], append(report.Chatrooms[i].Members, report.Chatrooms[i].Owner)); err != nil {
			return err
		}
	}
	if err := r.saveCollectorChatrooms(ctx, report.CollectorId, chatroomIds); err != nil {
		return err
	}
	if err := r.saveCollectorMembers(ctx, report.CollectorId, memberIds); err != nil {
		return err
	}
	return nil
}
